package upload;
import gui.mainView.UIView;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;

import com.google.gson.Gson;

import commons.Manager;
import commons.Media;
import commons.Paths;
import commons.User;
import commons.exceptions.UploadException;
import server.utilities.UploadMessage;

/**
 * Class that handles one POST request
 * @author Romain Pellerin
 */
public class Request extends Thread {
	private final static long FILE_MAX_SIZE		= 2500000000L;	// 2500 MB
	private final static long FILE_MIN_SIZE		= 1000000L;		// 1 MB
	private final static int BUFFER_SIZE		= 102400;		// 100 KB
	// CONSTANTS FOR THE RESPONSE
	private static final String RESULT	= "result";
	private static final String ERROR	= "error";

	private Socket clientSocket;
	private Server server;

	// HTTP Request
	private String boundary;
	private String contentLength;
	private String filename;
	
	private User user;

	/**
	 * Constructor
	 * @param socket The {@link Socket} to read from
	 * @param server The {@link Server} that initiated the socket
	 */
	public Request (Socket socket, Server server) {
		this.clientSocket = socket;
		this.server = server;
		this.setName("Client");
	}

	/**
	 * Read a line from an {@link InputStream}
	 * @param inFromClient The {@link InputStream} to read from
	 * @return The {@link String} read
	 * @throws IOException When something went wrong while reading
	 */
	private String readLine(InputStream inFromClient) throws IOException {
		StringBuilder lineb = new StringBuilder();
		int x = inFromClient.read();
		char c = x!=-1?(char) x:'\n';
		while (c != '\n'){
			lineb.append(c);
			x = inFromClient.read();
			c = (char)x;
			if (x == -1) break;
		}
		String line = lineb.toString();
		return line.substring(0,line.lastIndexOf('\r')<0?line.length():line.lastIndexOf('\r'));
	}

	@Override
	public void run() {
		System.out.println("New Request");
		InputStream is = null;
		PrintWriter out = null;
		String currentLine;
		FileOutputStream fos;
		try {
			is = clientSocket.getInputStream();
			out = new PrintWriter(clientSocket.getOutputStream(), true);
			if ((currentLine = readLine(is)) != null && currentLine.contains("POST /")) {	// waiting for the first line
				currentLine = currentLine.split("/")[1].split(" ")[0];
				user = Manager.getInstance().getUserList().getUserFromId(Integer.parseInt(currentLine));
				if (user == null) {
					throw new UploadException(UploadMessage.USER_UNKNOWN);
				}
				do { 																		// waiting for http headers + file headers
					currentLine = readLine(is);
					if (boundary == null && currentLine.contains("Content-Type: multipart/form-data")) {
						boundary = currentLine.split("boundary=")[1];
					}
					else if (contentLength == null && currentLine.contains("Content-Length:")) {
						contentLength = currentLine.split(" ")[1];
						long length = Long.valueOf(contentLength).longValue();
						/*long diskspace = -1L;
						try {
							diskspace = new File(".").getUsableSpace();
						}catch(Exception e){/*ignore}*/
						
						//if (diskspace != -1L && length+2000000L>=diskspace) throw new UploadException(UploadMessage.NO_ENOUGH_SPACE_AVAILABLE); // +2MB
						if (length > FILE_MAX_SIZE) throw new UploadException(UploadMessage.FILE_TOO_BIG);
						else if (length < FILE_MIN_SIZE) throw new UploadException(UploadMessage.FILE_TOO_SMALL);
					}
					else if (filename == null && currentLine.contains("filename")) {
						filename = currentLine.split("filename=")[1].replaceAll("\"", "");
						String[] filelist = filename.split("\\" + System.getProperty("file.separator"));
						filename = filelist[filelist.length - 1];  
						filename = filename.trim();
						if (filename.length() == 0) throw new UploadException(UploadMessage.FILE_MISSING);
					}
				}while(!(boundary != null && contentLength != null && filename != null && currentLine.equals("")));
				
				filename = renameFileIfExists(filename, Paths.AIRPI_UPLOADED_FILES);
				fos = new FileOutputStream(Paths.AIRPI_UPLOADED_FILES+filename);
			}
			else {
				throw new UploadException(UploadMessage.NOT_A_POST);
			}
		} catch (UploadException e) {
			sendResponse(is,out,e.getError());
			return;
		} catch (IOException e) {
			sendResponse(is,out,UploadMessage.UPLOAD_ABORTED);
			return;
		}
		
		int count = 0;
		Media media = null;
		try {
			int read;
			byte[] buffer = new byte[BUFFER_SIZE];
			
			while ((read = is.read(buffer)) != -1) {
				count++;
				fos.write(buffer, 0, read);
				fos.flush();
				if (count==50) {
					count++; // 51
					media = new Media(filename, Paths.AIRPI_UPLOADED_FILES, user, false);
					Manager.getInstance().addMedia(media);
				}
			}
			
			throw new SocketTimeoutException(); // may never be reached when 'Connection: keep-alive' is enabled client-side
			
		} catch (SocketTimeoutException e) {
			if (count<=50) {
				media = new Media(filename, Paths.AIRPI_UPLOADED_FILES, user, true);
				Manager.getInstance().addMedia(media);
			}
			else {
				if (media != null){
					media.setDownloadFinished(true);
					Manager.getInstance().updateIface(UIView.Update_MediaPan);
				}
			}
			
			sendResponse(is,out,UploadMessage.SUCCESS);
			
		} catch (IOException e) {
			sendResponse(is,out,UploadMessage.UPLOAD_ABORTED);
		}
		finally {
			try {fos.close();
			System.out.println("EndRequest");} catch (IOException e) {/*ignore*/}
		}
	}
	
	/**
	 * Sends an HTTP response to the client that will redirect him
	 * @param is The {@link InputStream} of the socket (where the HTTP request has been received)
	 * @param out The {@link PrinWriter} where to write the response (must be the {@link OutputStream} of the socket)
	 * @param uploadAborted The {@link UploadMesssage} to send as a GET parameter
	 */
	private void sendResponse(InputStream is, PrintWriter out, UploadMessage uploadAborted) {
		try {
			while (is.read(new byte[BUFFER_SIZE]) != -1) {} // flushes the InputStream
		} catch (Exception e) {/*ignore*/}
		
		if (out != null) {
			Map<String,Object> json_string = new LinkedHashMap<String,Object>();
			json_string.put(uploadAborted.equals(UploadMessage.SUCCESS) ? RESULT : ERROR, uploadAborted.getMessage());
			
			out.println("HTTP/1.1 200 OK\nAccess-Control-Allow-Origin: *\n\n"+new Gson().toJson(json_string));
			out.close();
		}
		
		try {is.close();} catch (Exception e) {/*ignore*/}
		try {clientSocket.close();} catch (Exception e) {/*ignore*/}
		server.removeOpenedSocket(clientSocket);
	}

	/**
	 * Returns a {@link String} corresponding to the new filename if another file exists with the same filename in the given directory
	 * @param name The name of the file (might change if another file with the same name already exists)
	 * @param path The directory where the file will be stored
	 * @return The {@link String} corresponding to the new filename
	 * @throws UploadException When a problem occurred with the directory
	 */
	public synchronized static String renameFileIfExists(String name, String path) throws UploadException {
		try {
			String old_name = new String(name);
			File folder = new File(path);
			File[] listOfFiles = folder.listFiles();
			ArrayList<String> list = new ArrayList<String>();
			for (int i = 0; i < listOfFiles.length; i++) {
				if (listOfFiles[i].isFile())
					list.add(listOfFiles[i].getName());
			}
			int i = 0;
			while (list.contains(name)) {
				name = "(" + (++i) + ") "+old_name;
			}
			return name;
		}
		catch (Exception e) {
			throw new UploadException(UploadMessage.DIRECTORY_ERROR);
		}
	}
}